#!/usr/bin/env python

import aptdaemon.client as client
import aptdaemon.errors as errors
import aptdaemon.enums as enums
from aptdaemon.enums import (
                            EXIT_SUCCESS,
                            EXIT_FAILED,
                            EXIT_CANCELLED,
                            STATUS_FINISHED,
                            ERROR_PACKAGE_DOWNLOAD_FAILED,
                            get_error_description_from_enum,
                            get_error_string_from_enum,
                            get_status_string_from_enum
                            )
from defer import inline_callbacks
from SystemUpdater.backend import InstallBackend
import logging
from gettext import gettext as _
from SystemUpdater.Core.utils import humanize_size
import dbus,time
from gi.repository import GLib
import traceback
from importlib import reload

UPDATER_IDLE_CHECK_INTERVAL = 5
INSTALL_IDLE_TIMEOUT = 700
UPDATE_IDLE_TIMEOUT = 5 * 60

class InstallBackendAptdaemon(InstallBackend):
    """Makes use of aptdaemon to refresh the cache and to install updates."""

    def __init__(self, window_main, action,mode = InstallBackend.MODE_DEFAULT_STATUS):
        InstallBackend.__init__(self, window_main, action,mode)
        self.window_main = window_main
        if self.window_main.aptd_lang_switch == True:
            self.window_main.aptd_lang_switch = False
            reload(client)
            reload(errors)
            reload(enums)

        self.client = client.AptClient()
        self._remove_check_inactivity = False

        if self.action == self.ACTION_UPDATE:
            self.update_timestamp = 0
            GLib.timeout_add_seconds(UPDATER_IDLE_CHECK_INTERVAL,
                self._check_update_inactivity)
            
            self.simulation_progress = 0
            self.tid = GLib.timeout_add_seconds(1,self._check_simulation_progress)
        
        elif self.action == self.ACTION_INSTALL or self.action == self.ACTION_INSTALL_SHUTDOWN:
            self.install_timestamp = INSTALL_IDLE_TIMEOUT
            self.check_progress = 0
            GLib.timeout_add_seconds(UPDATER_IDLE_CHECK_INTERVAL,
                self._check_install_inactivity)
        
    def _check_simulation_progress(self):
        self.simulation_progress += 20

        if self.aptd_base.progress >= 90 or self.simulation_progress > 80:
            return False
        else:
            self.aptd_base.progress = self.simulation_progress
            self._dist_status_changed()
            return True

    def _check_install_inactivity(self):
        """Shutdown the daemon if it has been inactive for time specified
        in INSTALL_IDLE_TIMEOUT.
        """
        logging.info("Checking for inactivity in Installing Time:%d...",self.install_timestamp)

        if self._remove_check_inactivity == True:
            logging.info("Need retry:Installing to exit and timeout check quit...")
            return False

        if self.window_main.now_working != self.ACTION_INSTALL and self.window_main.now_working != self.ACTION_INSTALL_SHUTDOWN:
            logging.info("Installing to exit and timeout check quit...")
            return False

        if self.aptd_base.progress !=  self.check_progress:
            self.check_progress = self.aptd_base.progress
            self.install_timestamp = INSTALL_IDLE_TIMEOUT

        if (self.install_timestamp <= 0 and self.aptd_base.on_installing == True):
            logging.error("Quitting due to inactivity(%s)",self.aptd_base.details)
            self._action_done(self.action,
                            is_cancelled=False, success=False,
                            #FIXME: 安装超时退出 
                            error_string=_("Could not install the upgrades"), 
                            error_desc=_("Installtion timeout to exit Due to inactivity") + self.aptd_base.details) 

            # self.dbus_send.Quit(None)
            return False
        else:
            self.install_timestamp = self.install_timestamp - 1

        return True

    def _check_update_inactivity(self):
        logging.info("Checking for inactivity in Updating...")
        if (self.aptd_base.cancelable == False and self.aptd_base.progress >= 90) or self.window_main.now_working != self.ACTION_UPDATE:
            logging.info("Updating to exit and timeout check quit...")
            return False

        timestamp = self.update_timestamp
        if self.aptd_base.cancelable == True and timestamp == 0:
            self.update_timestamp = time.time()
            return True
        
        if self.update_timestamp != 0 and  time.time() - self.update_timestamp > UPDATE_IDLE_TIMEOUT \
            and self.aptd_base.cancelable == True:
            logging.error("Quitting due to inactivity")
            self.dbus_send.transaction.cancel()
            return False
        
        return True

    @inline_callbacks
    def update(self):
        """刷新包cache"""
        try:
            trans = yield self.client.update_cache(defer=True)
            self.dbus_send.transaction = trans
            # 注册回调函数 接收更新的状态
            yield self._show_transaction(trans, self.ACTION_UPDATE,
                                        _("Checking for updates…"), False)
        except errors.NotAuthorizedError:
            self._action_done(self.ACTION_UPDATE,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception:
            self._action_done(self.ACTION_UPDATE,
                            is_cancelled=False, success=False,
                            error_string='', error_desc='')
            raise

    @inline_callbacks
    def commit(self,model,pkgs_install, pkgs_upgrade, pkgs_remove,pkgs_downgrade = []):
        """Commit a list of package adds and removes"""
        try:
            reinstall = purge = []
            trans = yield self.client.commit_packages(
                pkgs_install, reinstall, pkgs_remove, purge = purge, upgrade = pkgs_upgrade,
                downgrade = pkgs_downgrade,defer=True)
            self.dbus_send.transaction = trans
            
            yield self._show_transaction(trans, self.action,
                                        _("Installing updates…"), True)
        except errors.NotAuthorizedError:
            self._action_done(self.action,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except errors.TransactionFailed as e:
            logging.error(str(e))
        except dbus.DBusException as e:
            if e.get_dbus_name() != "org.freedesktop.DBus.Error.NoReply":
                raise
            self._action_done(self.action,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception:
            self._action_done(self.action,
                            is_cancelled=False, success=False,
                            error_string='', error_desc='')
            raise

    @inline_callbacks
    def commit_download(self,action,pkgs_install, pkgs_upgrade, pkgs_remove):
        """Commit a list of package adds and removes"""
        try:
            if action != self.ACTION_DOWNLOADONLY:
                self.aptd_base.end_progress = 50

            reinstall = purge = downgrade = []
            trans = yield self.client.commit_only(
                pkgs_install, reinstall, pkgs_remove, purge = purge, upgrade = pkgs_upgrade,\
                                downgrade = downgrade,download = self.ACTION_DOWNLOADONLY, defer=True)
            self.dbus_send.transaction = trans
            
            yield self._show_transaction_download(trans, self.action,
                                        _("Installing updates…"))

        except errors.NotAuthorizedError:
            self._action_done(self.action,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except errors.TransactionFailed as e:
            logging.error(str(e))
        except dbus.DBusException as e:
            if e.get_dbus_name() != "org.freedesktop.DBus.Error.NoReply":
                raise
            self._action_done(self.action,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception:
            self._action_done(self.action,
                            is_cancelled=False, success=False,
                            error_string='', error_desc='')
            raise

    @inline_callbacks
    def commit_install(self,pkgs_install, pkgs_upgrade, pkgs_remove,begin=0,end=100,final_stage=False):
        """Commit a list of package adds and removes"""
        try:
            reinstall = purge = downgrade =  []
            trans = yield self.client.commit_only(
                pkgs_install, reinstall, pkgs_remove, purge = purge, upgrade = pkgs_upgrade,
                downgrade = downgrade,download = self.ACTION_INSTALL_SHUTDOWN, defer=True)
            self.dbus_send.transaction = trans
            
            self.aptd_base.start_progress = begin
            self.aptd_base.end_progress = end
            yield self._show_transaction_steps(trans, self.action,
                                        _("Installing updates…"), final_stage)
                
        except errors.NotAuthorizedError:
            self._action_done(self.action,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except errors.TransactionFailed as e:
            logging.error(str(e))
        except dbus.DBusException as e:
            if e.get_dbus_name() != "org.freedesktop.DBus.Error.NoReply":
                raise
            self._action_done(self.action,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception:
            self._action_done(self.action,
                            is_cancelled=False, success=False,
                            error_string='', error_desc='')
            raise

    @inline_callbacks
    def install_deb(self,install_path,install_force):
        """安装deb包 """
        try:
            trans = yield self.client.install_file(path = install_path,force = install_force,defer=True)
            # 注册回调函数 接收更新的状态
            yield self._show_transaction(trans, self.ACTION_INSTALL_DEB,
                                        _("Installing deb packages…"), False)
        except errors.NotAuthorizedError:
            self._action_done(self.ACTION_INSTALL_DEB,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception as e:
            self._action_done(self.ACTION_INSTALL_DEB,
                            is_cancelled=False, success=False,
                            error_string=str(e), error_desc='', error_code=e.code)
            # raise

    @inline_callbacks
    def fix_broken(self):
        """安装deb包 """
        try:
            trans = yield self.client.fix_broken_depends(defer=True)
            self.dbus_send.transaction = trans
            # 注册回调函数 接收更新的状态
            yield self._show_transaction(trans, self.ACTION_FIX_BROKEN,
                                        _("Installing deb packages…"), False)
        except errors.NotAuthorizedError:
            self._action_done(self.ACTION_FIX_BROKEN,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception:
            self._action_done(self.ACTION_FIX_BROKEN,
                            is_cancelled=False, success=False,
                            error_string='', error_desc='')
            raise
    
    @inline_callbacks
    def fix_incomplete(self):
        """修复未完成的安装 """
        try:
            trans = yield self.client.fix_incomplete_install(defer=True)
            self.dbus_send.transaction = trans
            # 注册回调函数 接收更新的状态
            yield self._show_transaction(trans, self.ACTION_FIX_INCOMPLETE,
                                        _("fix incomplete install"), False)
        except errors.NotAuthorizedError:
            self._action_done(self.ACTION_FIX_INCOMPLETE,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception:
            self._action_done(self.ACTION_FIX_INCOMPLETE,
                            is_cancelled=False, success=False,
                            error_string='', error_desc='')
            raise

    @inline_callbacks
    def clean(self):
        """清空所有下载的文件 """
        try:
            trans = yield self.client.clean(defer=True)
            self.dbus_send.transaction = trans
            # 注册回调函数 接收更新的状态
            yield self._show_transaction(trans, self.ACTION_CLEAN,
                                        _("Remove all downloaded files."), False)
        except errors.NotAuthorizedError:
            self._action_done(self.ACTION_CLEAN,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception:
            self._action_done(self.ACTION_CLEAN,
                            is_cancelled=False, success=False,
                            error_string='', error_desc='')
            raise

    @inline_callbacks
    def purge_packages(self,pkgs_purge):
        """卸载deb包 """
        try:
            trans = yield self.client.commit_packages([],[],[],pkgs_purge,[],[],defer=True)
            self.dbus_send.transaction = trans
            # 注册回调函数 接收更新的状态
            yield self._show_transaction(trans, self.ACTION_REMOVE_PACKAGES,
                                        _("Installing deb packages…"), False)
        except errors.NotAuthorizedError:
            self._action_done(self.ACTION_REMOVE_PACKAGES,
                            authorized=False, success=False,
                            error_string='', error_desc='')
        except Exception as e:
            logging.error(str(e))

    #进度回调
    def _on_progress_changed(self, trans,progress,action):
        #不要101这种未知状态
        if progress == 101:
            return
        self.aptd_base.progress = progress
        self._dist_status_changed()

    #同步状态回调
    def _on_status_changed(self, trans, status,action):
        if action == self.ACTION_UPDATE and status == STATUS_FINISHED:
            return

        #转化词条
        self.aptd_base.status = get_status_string_from_enum(status)
        if self.aptd_base.status == None:
            return

        self._dist_status_changed()

    def _on_details_changed(self, trans, details,action):
        self.aptd_base.details = details
        self._dist_status_changed()

    # eta 剩余时间不正确，取消掉
    def _on_progress_download_changed(self,trans,current_items, total_items, currenty_bytes, total_bytes, current_cps, eta):
        if self.action == self.ACTION_INSTALL or self.action == self.ACTION_DOWNLOADONLY or self.action == self.ACTION_BACKGROUND_UPGRADE:
            if self.action == self.ACTION_INSTALL or self.action == self.ACTION_DOWNLOADONLY:
                self.aptd_base.current_items = current_items
                self.aptd_base.total_items = total_items
            self.dbus_send.UpdateDownloadInfo(self.now_upgrades.get_content(),\
                            current_items,  total_items, \
                            currenty_bytes, total_bytes, \
                            current_cps)
        else:
            if self.action == self.ACTION_UPDATE or self.action == self.ACTION_REMOVE_PACKAGES:
                return
            logging.info("Other Action:current_items = %d, total_items = %d, currenty_bytes = %s, total_bytes = %s, current_cps = %s/s",\
            current_items, total_items, \
            humanize_size(currenty_bytes), humanize_size(total_bytes),\
            humanize_size(current_cps))

    def _on_cancellable_changed(self, trans, Cancelable):
        #下面的这些 不发送取消信号
        if self.action == self.ACTION_REMOVE_PACKAGES:
            return

        if self.action != self.ACTION_UPDATE:
            logging.info("\033[1;32m" + "Emitting" + "\033[0m" +" Cancelable: %r",Cancelable) 
        self.dbus_send.Cancelable(Cancelable)
        self.aptd_base.cancelable = Cancelable

    def _on_config_file_conflict(self, transaction, old, new):
        logging.info("Config file conflict oldconf = %s , newconf = %s...",str(old),str(new))
        logging.info("Default To Replace Old Configfile...")

        transaction.resolve_config_file_conflict(old, "keep")

    #增加记录当产生错误的时候 详细信息 
    def _on_error_changed(self, trans,error_code, error_details):
        self.aptd_base.error_details = str(error_details)
        logging.error(str(error_details))

    #分发进度状态和细节信息
    def _dist_status_changed(self):
        progress = self.aptd_base.progress
        status = self.aptd_base.status
        details = self.aptd_base.details
        action = self.action

        if action == self.ACTION_UPDATE:
            if progress == 11:
                progress = 15

            if progress != 100:
                self.dbus_send.UpdateDetectStatusChanged(progress,status)
        elif action == self.ACTION_INSTALL:
            #在下载阶段发送取消信号
            if self.aptd_base.on_installing == False:
                self.dbus_send.Cancelable(self.aptd_base.cancelable)
            self.dbus_send.UpdateDloadAndInstStaChanged(self.now_upgrades.get_content(),progress,status,details)
        elif action == self.ACTION_DOWNLOADONLY:
            self.dbus_send.Cancelable(self.aptd_base.cancelable)
            self.dbus_send.UpdateDloadAndInstStaChanged(self.now_upgrades.get_content(),progress,status,details)
        elif action == self.ACTION_INSTALL_SHUTDOWN:
            # 写入进度 到plymouth
            self._progress_to_plymouth(progress)
            mesa = _("The system is installing updates. Please do not turn off your computer.  %d %%complete.")%progress
            self._message_to_plymouth(mesa)
            self.dbus_send.UpdateDloadAndInstStaChanged(self.now_upgrades.get_content(),progress,status,details)
        elif action == self.ACTION_FIX_BROKEN:
            self.dbus_send.FixBrokenStatusChanged(False,True,progress,status,'','')
        elif action == self.ACTION_REMOVE_PACKAGES:
            self.dbus_send.PurgePkgStatusChanged(progress,status,details)
        elif action == self.ACTION_INSTALL_DEB or action == self.ACTION_BACKGROUND_UPGRADE:
            self.dbus_send.InstalldebStatusChanged(progress,status,details)
        else:
            logging.info("Other Action:progress = %d , status = %s ,details = %s",progress,status,details)

    @inline_callbacks
    def _show_transaction_download(self, trans, action, header):
        trans.connect("finished", self._on_finished_download, action)
        #升级和更新的状态信息和进度
        trans.connect("status-changed", self._on_status_changed,action)
        trans.connect("progress-changed", self._on_progress_changed,action)
        #取消升级
        trans.connect("cancellable-changed", self._on_cancellable_changed)
        #下载的进度信息
        trans.connect("progress-details-changed", self._on_progress_download_changed)
        trans.connect("status-details-changed", self._on_details_changed,action)
        trans.connect("error", self._on_error_changed)

        trans.connect("config-file-conflict", self._on_config_file_conflict)

        yield trans.run()

    @inline_callbacks
    def _show_transaction_steps(self, trans, action, header, final_stage):
        if final_stage == True:
            trans.connect("finished", self._on_finished, action)
        else:
            trans.connect("finished", self._on_finished_steps, action)
        #升级和更新的状态信息和进度
        trans.connect("status-changed", self._on_status_changed,action)
        trans.connect("progress-changed", self._on_progress_changed,action)
        #取消升级
        trans.connect("cancellable-changed", self._on_cancellable_changed)
        #下载的进度信息
        trans.connect("progress-details-changed", self._on_progress_download_changed)
        trans.connect("status-details-changed", self._on_details_changed,action)
        trans.connect("error", self._on_error_changed)

        trans.connect("config-file-conflict", self._on_config_file_conflict)

        yield trans.run()

    @inline_callbacks
    def _show_transaction(self, trans, action, header, show_details):
        if action == self.ACTION_INSTALL_DEB:
            trans.connect("finished", self._on_finished_special, action)
        else:
            trans.connect("finished", self._on_finished, action)
        trans.connect("status-changed", self._on_status_changed,action)
        trans.connect("progress-changed", self._on_progress_changed,action)
        #取消升级
        trans.connect("cancellable-changed", self._on_cancellable_changed)
        #下载的进度信息
        trans.connect("progress-details-changed", self._on_progress_download_changed)
        trans.connect("status-details-changed", self._on_details_changed,action)
        trans.connect("error", self._on_error_changed)

        trans.connect("config-file-conflict", self._on_config_file_conflict)

        yield trans.run()

    def _on_finished_download(self, trans, status, action):
        try:
            if action != self.ACTION_DOWNLOADONLY and status == EXIT_SUCCESS:
                self._start_install_queue()
            elif status != EXIT_SUCCESS and trans.error != None and trans.error.code == ERROR_PACKAGE_DOWNLOAD_FAILED:
                if self.check_connectivity_pro() == True:
                    if self.window_main.retry_limit != 0:
                        logging.warning("Retry the upgrade and installaton and retry number: %d",self.window_main.retry_limit)
                        self._remove_check_inactivity = True
                        self.window_main.retry_limit = self.window_main.retry_limit - 1
                        self.window_main.start_install(self.now_upgrades.push_content)
                        return
                self._on_finished(trans, status, action)
            else:
                self._on_finished(trans, status, action)
        except Exception as e:
            logging.error(e)
            traceback.print_exc()

    def _on_finished_steps(self, trans, status, action):
        try:
            if status == EXIT_SUCCESS:
                self._start_install_queue()
            else:
                self._on_finished(trans, status, action)
        except Exception as e:
            logging.error(e)
            traceback.print_exc()

    def _on_finished(self, trans, status, action):
        try:
            error_string = ''
            error_desc = ''
            error_code = ''

            if status == EXIT_FAILED:
                # self.log_audit(str(trans.error.code))
                error_code = trans.error.code
                error_string = get_error_string_from_enum(trans.error.code)
                error_desc = get_error_description_from_enum(trans.error.code)
            #取消下载
            elif status == EXIT_CANCELLED:
                error_string = _("Failed to fetch")
                error_desc = _("_Cancel Upgrade")
            elif status == EXIT_SUCCESS and action == self.ACTION_REMOVE_PACKAGES:
                error_string = _("Uninstallation completed")

            is_success = (status == EXIT_SUCCESS)

            self._action_done(action,
                            is_cancelled=(status == EXIT_CANCELLED), success=is_success,
                            error_string=error_string, error_desc=error_desc,error_code=error_code)
        except Exception as e:
            logging.error(e)
            traceback.print_exc()
    
    def _on_finished_special(self, trans, status, action):
        try:
            error_string = ''
            error_desc = ''
            error_code = ''

            if status == EXIT_FAILED and action == self.ACTION_INSTALL_DEB:
                error_code = trans.error.code
                error_string = get_error_string_from_enum(trans.error.code)
                error_desc = get_error_description_from_enum(trans.error.code)
                if str(error_code) == 'error-dep-resolution-failed':
                    status,detailed_information,err_info = self.window_main.start_deb_resolver(self.database.deb_metadata['deblist'])

            is_success = ((status == EXIT_SUCCESS) or (status == 0))
            self._action_done(action,
                            is_cancelled=(status == EXIT_CANCELLED), success=is_success,
                            error_string=error_string, error_desc=error_desc,error_code=error_code)
        except Exception as e:
            logging.error(e)
            traceback.print_exc()

    def check_connectivity_pro(self):
        try:
            logging.info("Start checking the network...")
            obj = self.bus.get_object("org.freedesktop.NetworkManager","/org/freedesktop/NetworkManager")
            interface = dbus.Interface(obj, "org.freedesktop.NetworkManager")
            retval = interface.CheckConnectivity(timeout=15)
        except Exception as e:
            logging.error("Network status is Exception:" + str(e))
            return False

        if retval == 4:
            logging.info("Network status is Success...")
            return True
        else:
            logging.info("Network status(%d) is Exception...",retval)
            return False
    # def log_audit(self,error_code):
    #     if error_code == "error-package-manager-failed":
    #         error_msg = self.check_install_error()
            
    #     with open("/var/log/kylin-system-updater/error_details.log", 'w+') as configfile:
    #         configfile.write(str(error_msg))

    # def check_install_error(self):
    #     locate_error = ''
    #     with open("/var/log/apt/term.log", "r+") as f:
    #         log_start = None
    #         log_end = None
    #         aptterm_log = f.readlines()

    #         reverse_log = aptterm_log[::-1]

    #         for logstr in reverse_log:
    #             if log_end == None and "Log ended:" in logstr:
    #                 log_end = aptterm_log.index(logstr)
    #             if log_start == None and "Log started:" in logstr:
    #                 log_start = aptterm_log.index(logstr)

    #             if log_end != None and log_start != None:
    #                 break

    #         latest_log = aptterm_log[log_start:log_end+1]
    #         error_deb = latest_log[-2].strip()
            
    #         error_msg_line = None
    #         for log_msg in latest_log:
    #             if error_deb in log_msg:
    #                 error_msg_line = latest_log.index(log_msg)
    #                 locate_error = latest_log[error_msg_line-5:error_msg_line+5]
    #                 break
            
    #     return locate_error